package 泛型;

import java.lang.reflect.Array;

public class 通配符的使用 {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根

	}

}

【Java 泛型总结（一）：基本用法与类型擦除】

Java 在 1.5 引入了泛型机制，泛型本质是参数化类型，也就是说变量的类型是一个参数，在使用时再指定为具体类型。泛型可以用于类、接口、方法，通过使用泛型可以使代码更简单、
安全。然而 Java 中的泛型使用了类型擦除，所以只是伪泛型。这篇文章对泛型的使用以及存在的问题做个总结，主要参考自 《Java 编程思想》。


【基本用法】
泛型类
如果有一个类 Holder 用于包装一个变量，这个变量的类型可能是任意的，怎么编写 Holder 呢？在没有泛型之前可以这样：

public class Holder1 {
    private Object a;

    public Holder1(Object a) {
        this.a = a;
    }

    public void set(Object a) {
        this.a = a;
    }
    public Object get(){
        return a;
    }

    public static void main(String[] args) {
        Holder1 holder1 = new Holder1("not Generic");
        String s = (String) holder1.get();
        holder1.set(1);
        Integer x = (Integer) holder1.get();
    }
}

在 Holder1 中，有一个用 Object 引用的变量。因为任何类型都可以向上转型为 Object，所以这个 Holder 可以接受任何类型。在取出的时候 Holder 只知道它保存的
是一个 Object 对象，所以要强制转换为对应的类型。在 main 方法中， holder1 先是保存了一个字符串，也就是 String 对象，接着又变为保存一个 Integer 对
象(参数 1 会自动装箱)。从 Holder 中取出变量时强制转换已经比较麻烦，这里还要记住不同的类型，要是转错了就会出现运行时异常。

下面看看 Holder 的泛型版本：

public class Holder2<T> {

    private T a;
    public Holder2(T a) {
        this.a = a;
    }

    public T get() {
        return a;
    }

    public void set(T a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Holder2<String> holder2 = new Holder2<>("Generic");
        String s = holder2.get();

        holder2.set("test");
        holder2.set(1);//无法编译   参数 1 不是 String 类型

    }
}

在 Holder2 中， 变量 a 是一个参数化类型 T，T 只是一个标识，用其它字母也是可以的。创建 Holder2 对象的时候，在尖括号中传入了参数 T 的类型，那么在这个对
象中，所有出现 T 的地方相当于都用 String 替换了。现在的 get 的取出来的不是 Object ，而是 String 对象，因此不需要类型转换。另外，当调用 set 时，只
能传入 String 类型，否则编译无法通过。这就保证了 holder2 中的类型安全，避免由于不小心传入错误的类型。

通过上面的例子可以看出泛使得代码更简便、安全。引入泛型之后，Java 库的一些类，比如常用的容器类也被改写为支持泛型，我们使用的时候都会传入参数类型，
如：ArrayList<Integer> list = ArrayList<>();。

【泛型方法】
泛型不仅可以针对类，还可以单独使某个方法是泛型的，举个例子：

public class GenericMethod {
    public <K,V> void f(K k,V v) {
        System.out.println(k.getClass().getSimpleName());
        System.out.println(v.getClass().getSimpleName());
    }

    public static void main(String[] args) {
        GenericMethod gm = new GenericMethod();
        gm.f(new Integer(0),new String("generic"));
    }
}

代码输出：
    Integer
    String
GenericMethod 类本身不是泛型的，创建它的对象的时候不需要传入泛型参数，但是它的方法 f 是泛型方法。在返回类型之前是它的参数标识 <K,V>，注意这里有两个泛型
参数，所以泛型参数可以有多个。

调用泛型方法时可以不显式传入泛型参数，上面的调用就没有。这是因为编译器会使用参数类型推断，根据传入的实参的类型 (这里是 integer 和 String) 推断出 K 和 V 的
类型。

【类型擦除】
什么是类型擦除
Java 的泛型使用了类型擦除机制，这个引来了很大的争议，以至于 Java 的泛型功能受到限制，只能说是”伪泛型“。什么叫类型擦除呢？简单的说就是，类型参数只存在于编译
期，在运行时，Java 的虚拟机 ( JVM ) 并不知道泛型的存在。先看个例子：

public class ErasedTypeEquivalence {
    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1+" "+ c2);//class java.util.ArrayList class java.util.ArrayList
        System.out.println(c1 == c2);//true
    }
}
上面的代码有两个不同的 ArrayList：ArrayList<Integer> 和 ArrayList<String>。在我们看来它们的参数化类型不同，一个保存整型，一个保存字符串型。但是通过
比较它们的 Class 对象，上面的代码输出是 true。这说明在 JVM 看来它们是同一个类。而在 C++、C# 这些支持真泛型的语言中，它们就是不同的类。

泛型参数会擦除到它的第一个边界，比如说上面的 Holder2 类，参数类型是一个单独的 T，那么就擦除到 Object,相当于所有出现 T 的地方都用 Object 替换。所以
在 JVM 看来，保存的变量 a 还是 Object 类型。之所以取出来自动就是我们传入的参数类型，这是因为编译器在编译生成的字节码文件中插入了类型转换的代码，不需要我们
手动转型了。如果参数类型有边界那么就擦除到它的第一个边界，这个下一节再说。

【擦除带来的问题】
擦除会出现一些问题，下面是一个例子：

class HasF {
    public void f() {
        System.out.println("HasF.f()");
    }
}
class otherClass extends HasF{
	public void f() {
		System.out.println("otherClass.f()");
	}
}
//public class Manipulator<T> {//没有给出T的边界，出错，因为泛型擦除
public class Manipulator<T extends HasF> {//给T设置一个上界，凡是T处皆用实际代入的类替代（如：HasF，otherClass），只要代入的类为HasF及子类都可
    private T obj;

    public Manipulator(T obj) {
        this.obj = obj;
    }

    public void manipulate() {
   	 System.out.println(obj.getClass().getSimpleName());//显示HasF及otherClass，说明T分别用HasF和otherClass替代
       obj.f(); //当未设置上界时，无法编译 找不到符号 f()
    }

    public static void main(String[] args) {
        HasF hasF  = new HasF();
        Manipulator<HasF> manipulator = new Manipulator<>(hasF);//HasF类代入
        manipulator.manipulate();//HasF.f()
        
        HasF otherhasf  = new otherClass();
        Manipulator<HasF> manipulator1 = new Manipulator<>(otherhasf);//HasF类的子类otherClass代入
        manipulator1.manipulate();//otherClass.f()

    }
}

上面的 Manipulator 是一个泛型类，内部用一个泛型化的变量 obj，在 manipulate 方法中，调用了 obj 的方法 f()，但是这行代码无法编译。因为类型擦除，编译器
不确定 obj 是否有 f() 方法。解决这个问题的方法是给 T 一个边界:

class Manipulator2<T extends HasF> {
    private T obj;
    public Manipulator2(T x) { obj = x; }
    public void manipulate() { obj.f(); }
}
现在 T 的类型是 <T extends HasF>，这表示 T 必须是 HasF 或者 HasF 的导出类型(子类)。这样，调用 f() 方法才安全。HasF 就是 T 的边界，因此通过类型擦
除后，所有出现 T 的地方都用 HasF 替换。这样编译器就知道 obj 是有方法 f() 的。

但是这样就抵消了泛型带来的好处，上面的类完全可以改成这样：

class Manipulator3 {
    private HasF obj;
    public Manipulator3(HasF x) { obj = x; }
    public void manipulate() { obj.f(); }
}
所以泛型只有在比较复杂的类中才体现出作用。但是像 <T extends HasF> 这种形式的东西不是完全没有意义的。如果类中有一个返回 T 类型的方法，泛型就有用了，因为
这样会返回准确类型。比如下面的例子：

class ReturnGenericType<T extends HasF> {
    private T obj;
    public ReturnGenericType(T x) { obj = x; }
    public T get() { return obj; }
}
这里的 get() 方法返回的是泛型参数的准确类型，而不是 HasF。

【类型擦除的补偿】
类型擦除导致泛型丧失了一些功能，任何在运行期需要知道确切类型的代码都无法工作。比如下面的例子：

public class Erased<T> {
    private final int SIZE = 100;
    public static void f(Object arg) {
    if(arg instanceof T) {} // Error
    T var = new T(); // Error
    T[] array = new T[SIZE]; // Error
    T[] array = (T)new Object[SIZE]; // Unchecked warning
    }
}
通过 new T() 创建对象是不行的，一是由于类型擦除，二是由于编译器不知道 T 是否有默认的构造器。一种解决的办法是传递一个工厂对象并且通过它创建新的实例。

interface FactoryI<T> {
	T create();//最终目的是生成泛型T的实例
}

class Foo2<T> {
	private T x;
	public <F extends FactoryI<T>> Foo2(F factory) {//泛型入参Factory类及子类皆可
		x = factory.create();
	}
	// ...
}

class IntegerFactory implements FactoryI<Integer> {
	public Integer create() {// Integer -->T 并实现接口FactoryI方法
		return Integer.valueOf(0);
	}
}

class Widget {
	public static class Factory implements FactoryI<Widget> {
		public Widget create() {
			return new Widget();
		}
	}
}

public class FactoryConstraint {
	public static void main(String[] args) {
		new Foo2<Integer>(new IntegerFactory());
		new Foo2<Widget>(new Widget.Factory());
	}
}
另一种解决的方法是利用模板设计模式：

abstract class GenericWithCreate<T> {//生成XX类实例：即生成泛型类实例
    final T element;
    GenericWithCreate() { element = create(); }
    abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
    X create() { return new X(); }
    void f() {
    System.out.println(element.getClass().getSimpleName());
    }
}
public class CreatorGeneric {
    public static void main(String[] args) {
        Creator c = new Creator();
        c.f();
    }
}
具体类型的创建放到了子类继承父类时，在 create 方法中创建实际的类型并返回。

总结
本文介绍了 Java 泛型的使用，以及类型擦除相关的问题。一般情况下泛型的使用比较简单，但是某些情况下，尤其是自己编写使用泛型的类或者方法时要注意类型擦除的问题。接下来
会介绍数组与泛型的关系以及通配符的使用，有兴趣的读者可进入下一篇：Java 泛型总结（二）：泛型与数组。

【Java 泛型总结（二）：泛型与数组】

【简介】
上一篇文章介绍了泛型的基本用法以及类型擦除的问题，现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的，主要体现在三个方面：

1.数组创建后大小便固定，但效率更高
2.数组能追踪它内部保存的元素的具体类型，插入的元素类型会在编译期得到检查
3.数组可以持有原始类型 ( int，float等 )，不过有了自动装箱，容器类看上去也能持有原始类型了

那么当数组遇到泛型会怎样？ 能否创建泛型数组呢？这是这篇文章的主要内容。

【泛型数组】
如何创建泛型数组

如果有一个类如下：

 class Generic<T> {
    
}
如果要创建一个泛型数组，应该是这样： Generic<Integer> ga = new Generic<Integer>[]。不过这行代码会报错，也就是说不能直接创建泛型数组。

那么如果要使用泛型数组怎么办？一种方案是使用 ArrayList，比如下面的例子：

public class ListOfGenerics<T> {
    private List<T> array = new ArrayList<T>();
    public void add(T item) { array.add(item); }
    public T get(int index) { return array.get(index); }
}
如何创建真正的泛型数组呢？我们不能直接创建，但可以定义泛型数组的引用。比如：

public class ArrayOfGenericReference {
    static Generic<Integer>[] gia;
}
gia 是一个指向泛型数组的引用，这段代码可以通过编译。但是，我们并不能创建这个确切类型的数组，也就是不能使用 new Generic<Integer>[]。具体参见下面的例子：

public class ArrayOfGeneric {
    static final int SIZE = 100;
    static Generic<Integer>[] gia;
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        // Compiles; produces ClassCastException:
        //! gia = (Generic<Integer>[])new Object[SIZE];
        // Runtime type is the raw (erased) type:
        gia = (Generic<Integer>[])new Generic[SIZE];
        System.out.println(gia.getClass().getSimpleName());
        gia[0] = new Generic<Integer>();
        //! gia[1] = new Object(); // Compile-time error
        // Discovers type mismatch at compile time:
        //! gia[2] = new Generic<Double>();
        Generic<Integer> g = gia[0];
    }
} /*输出：
Generic[]
*///:~
数组能追踪元素的实际类型，这个类型是在数组创建的时候建立的。上面被注释掉的一行代码： gia = (Generic<Integer>[])new Object[SIZE]，数组在创建的时候
是一个 Object 数组，如果转型便会报错。成功创建泛型数组的唯一方式是创建一个类型擦除的数组，然后转型，
如代码： gia = (Generic<Integer>[])new Generic[SIZE]。gia 的 Class 对象输出的名字是 Generic[]。

我个人的理解是：由于类型擦除，所以 Generic<Integer> 相当于初始类型 Generic，那么 gia = (Generic<Integer>[])new Generic[SIZE] 中的转型其
实还是转型为 Generic[]，看上去像没转，但是多了编译器对参数的检查和自动转型，向数组插入 new Object() 和 new Generic<Double>() 均会报错，
而 gia[0] 取出给 Generic<Integer> 也不需要我们手动转型。

使用 T[] array
上面的例子中，元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子：

public class GenericArray<T> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(int sz) {
        array = (T[])new Object[sz];   // 创建泛型数组
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Method that exposes the underlying representation:
    public T[] rep() { return array; }     //返回数组 会报错
    public static void main(String[] args) {
        GenericArray<Integer> gai =
        new GenericArray<Integer>(10);
        // This causes a ClassCastException:
        //! Integer[] ia = gai.rep();
        // This is OK:
        Object[] oa = gai.rep();
    }
}
在上面的代码中，泛型数组的创建是创建一个 Object 数组，然后转型为 T[]。但数组实际的类型还是 Object[]。在调用 rep()方法的时候，就
报 ClassCastException 异常了，因为 Object[] 无法转型为 Integer[]。

那创建泛型数组的代码 array = (T[])new Object[sz] 为什么不会报错呢？我的理解和前面介绍的类似，由于类型擦除，相当于转型为 Object[]，看上去就是没转，但
是多了编译器的参数检查和自动转型。而如果把泛型参数改成 <T extends Integer>，那么因为类型是擦除到第一个边界，所以 array = (T[])new Object[sz] 中相
当于转型为 Integer[]，这应该会报错。下面是实验的代码：

public class GenericArray<T extends Integer> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(int sz) {
        array = (T[])new Object[sz];   // 创建泛型数组
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Method that exposes the underlying representation:
    public T[] rep() { return array; }     //返回数组 会报错
    public static void main(String[] args) {
        GenericArray<Integer> gai =
        new GenericArray<Integer>(10);
        // This causes a ClassCastException:
        //! Integer[] ia = gai.rep();
        // This is OK:
        Object[] oa = gai.rep();
    }
}
相比于原始的版本，上面的代码只修改了第一行，把 <T> 改成了 <T extends Integer>，那么不用调用 rep()，在创建泛型数组的时候就会报错。下面是运行结果：

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at GenericArray.<init>(GenericArray.java:15)
使用 Object[] array
由于擦除，运行期的数组类型只能是 Object[]，如果我们立即把它转型为 T[]，那么在编译期就失去了数组的实际类型，编译器也许无法发现潜在的错误。因此，更好的办法是在
内部最好使用 Object[] 数组，在取出元素的时候再转型。看下面的例子：

public class GenericArray2<T> {
    private Object[] array;
    public GenericArray2(int sz) {
        array = new Object[sz];
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    @SuppressWarnings("unchecked")
    public T get(int index) { return (T)array[index]; }
    @SuppressWarnings("unchecked")
    public T[] rep() {
        return (T[])array; // Warning: unchecked cast
    }
    public static void main(String[] args) {
        GenericArray2<Integer> gai =new GenericArray2<Integer>(10);
        for(int i = 0; i < 10; i ++) gai.put(i, i);
        for(int i = 0; i < 10; i ++) System.out.print(gai.get(i) + " ");
        System.out.println();
        try {
            Integer[] ia = gai.rep();
        } catch(Exception e) { System.out.println(e); }
    }
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~
现在内部数组的呈现不是 T[] 而是 Object[]，当 get() 被调用的时候数组的元素被转型为 T，这正是元素的实际类型。不过调用 rep() 还是会报错， 因为数组的实际类
型依然是Object[]，终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型，以至于不小心引入错误。

【使用类型标识】
其实使用 Class 对象作为类型标识是更好的设计：

public class GenericArrayWithTypeToken<T> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int sz) {
   	 //class Array {
   	 //	 ...
   	 //	 public static Object newInstance(Class<?> componentType, int length)
   	 //			 throws NegativeArraySizeException {
   	 //       	return newArray(componentType, length);
   	 //   	}
   	 //	 ...
   	 //}
        array = (T[])Array.newInstance(type, sz);//调用Array静态方法，生成数组实例
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Expose the underlying representation:
    public T[] rep() { return array; }
    public static void main(String[] args) {
        GenericArrayWithTypeToken<Integer> gai =
        new GenericArrayWithTypeToken<Integer>(
        Integer.class, 10);
        // This now works:
        Integer[] ia = gai.rep();
    }
}

在构造器中传入了 Class<T> 对象，通过 Array.newInstance(type, sz) 创建一个数组，这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数
组的元素类型便不再是 Object，而是 T。这个方法返回 Object 对象，需要把它转型为数组。不过其他操作都不需要转型了，包括 rep() 方法，因为数组的实际类型
与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。

/*
//分别创建Integer、String、Person型数组
public class Test1<T> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public Test1(Class<T> type, int sz) {//构造函数，通过 Array.newInstance(type, sz) 创建一个数组
        array = (T[])Array.newInstance(type, sz);
    }
    public void put(int index, T item) {//根据数组索引号，放入数据
        array[index] = item;
    }
    public T get(int index) {//根据索引号，获得数据
    	return array[index]; 
    }
    // Expose the underlying representation:
    public T[] rep() {//转换相应成类型数组
    	return array; 
    }
    public static void main(String[] args) {
        Test1<Integer> gai =new Test1<Integer>(Integer.class, 10);//创建整型数组
        for(int i = 0; i < 10; i ++) {gai.put(i, i);}//放入数据
        for(int i = 0; i < 10; i ++) {System.out.print(gai.get(i) + " ");}
        System.out.println();
        Integer[] ia = gai.rep();
        for(int i = 0; i < 10; i ++) {System.out.print(ia[i] + " ");}
        System.out.println();
        
        Test1<String> ga =new Test1<String>(String.class, 10);//创建String型数组
        for(int i = 0; i < 10; i ++) {ga.put(i, String.valueOf(i));}
        for(int i = 0; i < 10; i ++) {System.out.print(gai.get(i) + " ");}
        System.out.println();
        String[] ias = ga.rep();
        for(int i = 0; i < 10; i ++) {System.out.print(ias[i] + " ");}
        System.out.println();
        
        Test1<Person> persons=new Test1<Person>(Person.class, 4);//创建Person型数组
        persons.put(0, new Person(1, "张三"));
        persons.put(1, new Person(2, "李四"));
        persons.put(2, new Person(3, "王五"));
        for (int i=0;i<3;i++) System.out.println(persons.get(i));
        System.out.println();
        Person[] iap = persons.rep();
        for(int i = 0; i < 3; i ++) {System.out.print(iap[i] + " ");}
        System.out.println();
        
    }
}
class Person{
	Integer id;
	String name;

	public Person(Integer id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	public String toString() {
		return "[ id="+id+" name="+name+" ]";
	}
}

 */


总结
数组与泛型的关系还是有点复杂的，Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解，如果错误希望大家指正。
下一篇会总结通配符的使用，有兴趣的读者可进入下一篇：Java 泛型总结（三）：通配符的使用。

参考

【Java 泛型总结（三）：通配符的使用】
【简介】
前两篇文章介绍了泛型的基本用法、类型擦除以及泛型数组。在泛型的使用中，还有个重要的东西叫通配符，本文介绍通配符的使用。

【数组的协变】
在了解通配符之前，先来了解一下数组。Java 中的数组是协变的，什么意思？看下面的例子：

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

public class CovariantArrays {
    public static void main(String[] args) {       
        Fruit[] fruit = new Apple[10];
        fruit[0] = new Apple(); // OK
        fruit[1] = new Jonathan(); // OK
        // Runtime type is Apple[], not Fruit[] or Orange[]:
        try {
            // Compiler allows you to add Fruit:
            fruit[0] = new Fruit(); // ArrayStoreException
        } catch(Exception e) { System.out.println(e); }
        try {
            // Compiler allows you to add Oranges:
            fruit[0] = new Orange(); // ArrayStoreException
        } catch(Exception e) { System.out.println(e); }
        }
} /* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
*///:~
main 方法中的第一行，创建了一个 Apple 数组并把它赋给 Fruit 数组的引用。这是有意义的，Apple 是 Fruit 的子类，一个 Apple 对象也是一种 Fruit 对象，
所以一个 Apple 数组也是一种 Fruit 的数组。这称作数组的协变，Java 把数组设计为协变的，对此是有争议的，有人认为这是一种缺陷。

尽管 Apple[] 可以 “向上转型” 为 Fruit[]，但数组元素的实际类型还是 Apple，我们只能向数组中放入 Apple或者 Apple 的子类。在上面的代码中，向数组中放入
了 Fruit 对象和 Orange 对象。对于编译器来说，这是可以通过编译的，但是在运行时期，JVM 能够知道数组的实际类型是 Apple[]，所以当其它对象加入数组的时候就会
抛出异常。

泛型设计的目的之一是要使这种运行时期的错误在编译期就能发现，看看用泛型容器类来代替数组会发生什么：

Fruit fruit1=new Apple();//可通过编译，Apple是Fruit子类
// Compile Error: incompatible types:
ArrayList<Fruit> flist = new ArrayList<Apple>();//无法编译，ArrayList<Apple> 不是 ArrayList<Fruit> 的
上面的代码根本就无法编译。当涉及到泛型时， 尽管 Apple 是 Fruit 的子类型，但是 ArrayList<Apple> 不是 ArrayList<Fruit> 的子类型，泛型不支持协变。

【使用通配符】
从上面我们知道，List<Number> list = ArrayList<Integer> 这样的语句是无法通过编译的，尽管 Integer 是 Number 的子类型。那么如果我们确实需要建立这
种 “向上转型” 的关系怎么办呢？这就需要通配符来发挥作用了。

【上边界限定通配符】
利用 <? extends Fruit> 形式的通配符，可以实现泛型的向上转型：

public class GenericsAndCovariance {
    public static void main(String[] args) {
        // Wildcards allow covariance:
        List<? extends Fruit> flist = new ArrayList<Apple>();
        // Compile Error: can’t add any type of object:
        // flist.add(new Apple());
        // flist.add(new Fruit());
        // flist.add(new Object());
        flist.add(null); // Legal but uninteresting
        // We know that it returns at least Fruit:
        Fruit f = flist.get(0);
    }
}
上面的例子中， flist 的类型是 List<? extends Fruit>，我们可以把它读作：一个类型的 List， 这个类型可以是继承了 Fruit 的某种类型。注意，这并不是说这
个 List 可以持有 Fruit 的任意类型。通配符代表了一种特定的类型，它表示 “某种特定的类型，但是 flist 没有指定”。这样不太好理解，具体针对这个例子解释就
是，flist 引用可以指向某个类型的 List，只要这个类型继承自 Fruit，可以是 Fruit 或者 Apple，比如例子中的 new ArrayList<Apple>，但是为了向上转型
给 flist，flist 并不关心这个具体类型是什么。

如上所述，通配符 List<? extends Fruit> 表示某种特定类型 ( Fruit 或者其子类 ) 的 List，但是并不关心这个实际的类型到底是什么，反正是 Fruit 的子类
型，Fruit 是它的上边界。那么对这样的一个 List 我们能做什么呢？其实如果我们不知道这个 List 到底持有什么类型，怎么可能安全的添加一个对象呢？在上面的代码中，
向 flist 中添加任何对象，无论是 Apple 还是 Orange 甚至是 Fruit 对象，编译器都不允许，唯一可以添加的是 null。所以如果做了泛型的向上转
型 (List<? extends Fruit> flist = new ArrayList<Apple>())，那么我们也就失去了向这个 List 添加任何对象的能力，即使是 Object 也不行。

另一方面，如果调用某个返回 Fruit 的方法，这是安全的。因为我们知道，在这个 List 中，不管它实际的类型到底是什么，但肯定能转型为 Fruit，所以编译器允许返
回 Fruit。

了解了通配符的作用和限制后，好像任何接受参数的方法我们都不能调用了。其实倒也不是，看下面的例子：

public class CompilerIntelligence {
    public static void main(String[] args) {
        List<? extends Fruit> flist = Arrays.asList(new Apple());
        Apple a = (Apple)flist.get(0); // No warning
        flist.contains(new Apple()); // Argument is ‘Object’
        flist.indexOf(new Apple()); // Argument is ‘Object’
        
        //flist.add(new Apple());   无法编译

    }
}
在上面的例子中，flist 的类型是 List<? extends Fruit>，泛型参数使用了受限制的通配符，所以我们失去了向其中加入任何类型对象的例子，最后一行代码无法编译。

但是 flist 却可以调用 contains 和 indexOf 方法，它们都接受了一个 Apple 对象做参数。如果查看 ArrayList 的源代码，可以发现 add() 接受一个泛型类型
作为参数，但是 contains 和 indexOf 接受一个 Object 类型的参数，下面是它们的方法签名：

public boolean add(E e)
public boolean contains(Object o)
public int indexOf(Object o)
所以如果我们指定泛型参数为 <? extends Fruit> 时，add() 方法的参数变为 ? extends Fruit，编译器无法判断这个参数接受的到底是 Fruit 的哪种类型，所以它
不会接受任何类型。

然而，contains 和 indexOf 的类型是 Object，并没有涉及到通配符，所以编译器允许调用这两个方法。这意味着一切取决于泛型类的编写者来决定那些调用是 “安全” 的，
并且用 Object 作为这些安全方法的参数。如果某些方法不允许类型参数是通配符时的调用，这些方法的参数应该用类型参数，比如 add(E e)。

当我们自己编写泛型类时，上面介绍的就有用了。下面编写一个 Holder 类：

public class Holder<T> {
    private T value;
    public Holder() {}
    public Holder(T val) { value = val; }
    public void set(T val) { value = val; }
    public T get() { return value; }
    public boolean equals(Object obj) {
    return value.equals(obj);
    }
    public static void main(String[] args) {
        Holder<Apple> Apple = new Holder<Apple>(new Apple());
        Apple d = Apple.get();
        Apple.set(d);
        // Holder<Fruit> Fruit = Apple; // Cannot upcast
        Holder<? extends Fruit> fruit = Apple; // OK
        Fruit p = fruit.get();
        System.out.println(p.getClass().getSimpleName());//Apple
        d = (Apple)fruit.get(); // Returns ‘Object’
        try {
            Orange c = (Orange)fruit.get(); // No warning
        } catch(Exception e) { System.out.println(e); }
        // fruit.set(new Apple()); // Cannot call set()
        // fruit.set(new Fruit()); // Cannot call set()
        System.out.println(fruit.equals(d)); // OK
    }
} /* Output: (Sample)
java.lang.ClassCastException: Apple cannot be cast to Orange
true
*///:~
在 Holer 类中，set() 方法接受类型参数 T 的对象作为参数，get() 返回一个 T 类型，而 equals() 接受一个 Object 作为参数。fruit 的类型
是 Holder<? extends Fruit>，所以set()方法不会接受任何对象的添加，但是 equals() 可以正常工作。

【下边界限定通配符】
通配符的另一个方向是　“超类型的通配符“: ? super T，T 是类型参数的下界。使用这种形式的通配符，我们就可以 ”传递对象” 了。还是用例子解释：

public class SuperTypeWildcards {
    static void writeTo(List<? super Apple> apples) {
        apples.add(new Apple());
        apples.add(new Jonathan());
        // apples.add(new Fruit()); // Error
    }
}
writeTo 方法的参数 apples 的类型是 List<? super Apple>，它表示某种类型的 List，这个类型是 Apple 的基类型。也就是说，我们不知道实际类型是什么，
但是这个类型肯定是 Apple 的父类型。因此，我们可以知道向这个 List 添加一个 Apple 或者其子类型的对象是安全的，这些对象都可以向上转型为 Apple。但是我们
不知道加入 Fruit 对象是否安全，因为那样会使得这个 List 添加跟 Apple 无关的类型。

在了解了子类型边界和超类型边界之后，我们就可以知道如何向泛型类型中 “写入” ( 传递对象给方法参数) 以及如何从泛型类型中 “读取” ( 从方法中返回对象 )。下面是
一个例子：

public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i=0; i<src.size(); i++) 
        dest.set(i,src.get(i)); 
  } 
}
src 是原始数据的 List，因为要从这里面读取数据，所以用了上边界限定通配符：<? extends T>，取出的元素转型为 T。dest 是要写入的目标 List，所以用了下边界
限定通配符：<? super T>，可以写入的元素类型是 T 及其子类型。

【无边界通配符】
还有一种通配符是无边界通配符，它的使用形式是一个单独的问号：List<?>，也就是没有任何限定。不做任何限制，跟不用类型参数的 List 有什么区别呢？

List<?> list 表示 list 是持有某种特定类型的 List，但是不知道具体是哪种类型。那么我们可以向其中添加对象吗？当然不可以，因为并不知道实际是哪种类型，所以不
能添加任何类型，这是不安全的。而单独的 List list ，也就是没有传入泛型参数，表示这个 list 持有的元素的类型是 Object，因此可以添加任何类型的对象，只不过
编译器会有警告信息。

总结
通配符的使用可以对泛型参数做出某些限制，使代码更安全，对于上边界和下边界限定的通配符总结如下：

使用 List<? extends C> list 这种形式，表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象，这个对象包含的元素类型是 C 的子类
型 ( 包含 C 本身）的一种。
使用 List<? super C> list 这种形式，表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象，这个对象包含的元素就类型是 C 的超类
型 ( 包含 C 本身 ) 的一种。
大多数情况下泛型的使用比较简单，但是如果自己编写支持泛型的代码需要对泛型有深入的了解。这几篇文章介绍了泛型的基本用法、类型擦除、泛型数组以及通配符的使用，涵
盖了最常用的要点，泛型的总结就写到这里。


